package cn.matezk.cloud.service.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.Method;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.matezk.cloud.beans.PaymentNotify;
import cn.matezk.cloud.beans.PaymentOrderRequest;
import cn.matezk.cloud.dao.*;
import cn.matezk.cloud.dto.PaymentOrderDto;
import cn.matezk.cloud.framework.RedisMutexLock;
import cn.matezk.cloud.model.*;
import cn.matezk.cloud.service.IPaymentOrderService;
import cn.matezk.cloud.utils.StringUtils;
import cn.matezk.cloud.vo.PaymentOrderDetailVo;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.tencentcloudapi.iotexplorer.v20190423.IotexplorerClient;
import com.tencentcloudapi.iotexplorer.v20190423.models.ControlDeviceDataRequest;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import jdk.nashorn.internal.runtime.regexp.joni.exception.InternalException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

@Service
@Slf4j
public class PaymentOrderService implements IPaymentOrderService {
    @Autowired
    private PaymentOrderDao paymentOrderDao;
    @Autowired
    private PaymentOrderDetailDao paymentOrderDetailDao;
    @Autowired
    private UserDao userDao;
    @Autowired
    private DeviceDao deviceDao;
    @Autowired
    private ChargeStandardDao chargeStandardDao;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisMutexLock mutexLock;
    @Autowired
    private IotexplorerClient tencentClient;
    @Value("${tencent.iot-app-id}")
    private String iotAppId;
    @Value("${mini-app.appid}")
    private String miniAppId;
    @Value("${payment.merchantId}")
    private String merchantId;
    @Value("${payment.privateKeyPath:}")
    private String privateKeyPath;
    @Value("${payment.merchantSerialNumber:}")
    private String merchantSerialNumber;
    @Value("${payment.apiV3Key:}")
    private String apiV3Key;
    @Value("${payment.notify-path}")
    private String notifyPath;
    @Value("${tencent.notify-device:}")
    private String notifyDeviceId;

    @Override
    @Transactional
    public JSONObject order(PaymentOrderRequest paymentOrder, HttpServletRequest request) {
        // 查询用户是否存在
        User user = userDao.selectByUserId(paymentOrder.getUserId());
        if (ObjectUtil.isNull(user)) {
            user = new User();
            user.setAppId(iotAppId);
            user.setUnionId(paymentOrder.getUnionId());
            user.setUserId(paymentOrder.getUserId());
            userDao.insertSelective(user);
        } else {
            if (ObjectUtil.isEmpty(user.getUserId())) {
                user.setUserId(paymentOrder.getUserId());
                userDao.updateByPrimaryKeySelective(user);
            }
        }
        String orderId = IdUtil.fastSimpleUUID();
        paymentOrder.setOrderId(orderId);
        paymentOrder.setAppId(iotAppId);
        // 生成订单编号
        String date = DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
        String redisKey = String.format("order.count.%s", date);
        String orderNo = paymentOrder.getChargeType().concat(date).concat(String.format("%04d", increment(redisKey)));
        paymentOrder.setOrderNo(orderNo);
        paymentOrder.setOrderTime(new Date());
        String payerId = request.getHeader("x-wx-openid");
        paymentOrder.setPayer(payerId);
        paymentOrderDao.insertSelective(paymentOrder);
        for (PaymentOrderDetail detail : paymentOrder.getDetail()) {
            detail.setOrderId(orderId);
            detail.setOrderDetailId(IdUtil.fastSimpleUUID());
        }
        paymentOrderDetailDao.insertBatch(paymentOrder.getDetail());

        if (notifyPath.startsWith("http")) {
            RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder()
                    .merchantId(merchantId)
                    .privateKeyFromPath(privateKeyPath)
                    .merchantSerialNumber(merchantSerialNumber)
                    .apiV3Key(apiV3Key)
                    .build();
            JsapiService appService = new JsapiService.Builder().config(config).build();
            PrepayRequest prepayRequest = new PrepayRequest();
            prepayRequest.setAppid(miniAppId);
            prepayRequest.setMchid(merchantId);
            prepayRequest.setDescription(paymentOrder.getRemark());
            prepayRequest.setOutTradeNo(orderId);
            prepayRequest.setNotifyUrl(notifyPath);
            Amount amount = new Amount();
            amount.setTotal(paymentOrder.getAmount());
            amount.setCurrency("CNY");
            prepayRequest.setAmount(amount);
            Payer payer = new Payer();
            payer.setOpenid(payerId);
            prepayRequest.setPayer(payer);
            PrepayResponse response = appService.prepay(prepayRequest);
            return sign(config, miniAppId, response.getPrepayId());
        } else {
            JSONObject params = JSONUtil.createObj()
                    .set("body", paymentOrder.getRemark())
                    .set("openid", payerId)
                    .set("out_trade_no", orderId)
                    .set("spbill_create_ip", request.getHeader("x-real-ip"))
                    .set("env_id", request.getHeader("x-wx-env"))
                    .set("sub_mch_id", merchantId)
                    .set("total_fee", paymentOrder.getAmount())
                    .set("callback_type", 2)
                    .set("container", JSONUtil.createObj().set("service", request.getHeader("x-wx-service")).set("path", notifyPath));
            HttpRequest httpRequest = new HttpRequest("http://api.weixin.qq.com/_/pay/unifiedorder")
                    .method(Method.POST)
                    .header("Content-Type", "application/json;charset=utf8")
                    .body(params.toString());
            log.info(params.toString());
            HttpResponse response = httpRequest.execute();
            log.info("{}: {}", response.getStatus(), response.body());
            if (response.isOk()) {
                return JSONUtil.parseObj(response.body());
            } else {
                throw new InternalException(String.format("订单创建失败(%d)", response.getStatus()));
            }
        }
    }

    @Override
    public PageInfo<PaymentOrder> query(PaymentOrderDto paymentOrderDto) {
        PageHelper.startPage(paymentOrderDto.getPageNo(), paymentOrderDto.getPageSize());
        List<PaymentOrder> order = paymentOrderDao.query(paymentOrderDto);
        return new PageInfo<>(order);
    }

    @Override
    public List<PaymentOrderDetailVo> detail(String orderId) {
        return paymentOrderDetailDao.selectByOrderId(orderId);
    }

    @Override
    @Transactional
    public void notify(PaymentNotify notify) throws InterruptedException {
        String orderLockKey = PaymentOrder.createLockKey(notify.getOutTradeNo());
        if (mutexLock.tryLock(orderLockKey, 20)) {
            try {
                PaymentOrder paymentOrder = paymentOrderDao.selectByPrimaryKey(notify.getOutTradeNo());
                paymentOrder.setState(notify.getResultCode());
                paymentOrder.setStateDesc(notify.getErrCode());
                paymentOrder.setSuccessTime(notify.getSuccessTime());
                int count = paymentOrderDao.updateByPrimaryKeySelective(paymentOrder);
                if (count > 0 && PaymentOrder.STATE_SUCCESS.equals(notify.getResultCode())) {
                    List<PaymentOrderDetailVo> details = paymentOrderDetailDao.selectByOrderId(paymentOrder.getOrderId());
                    for (PaymentOrderDetailVo item : details) {
                        String deviceLockKey = Device.createLockKey(item.getDeviceId());
                        if (mutexLock.tryLock(deviceLockKey, 3)) {
                            try {
                                Device device = deviceDao.selectByPrimaryKey(item.getDeviceId());
                                if (PaymentOrder.TYPE_NF.equals(paymentOrder.getChargeType())) {
                                    Calendar calendar = Calendar.getInstance();
                                    calendar.setTime(device.getExpireDate());
                                    if (ObjectUtil.isNull(item.getMonths()) || item.getMonths().intValue() == 0) {
                                        calendar.add(Calendar.YEAR, item.getAmount() / 1000);
                                    } else {
                                        calendar.add(Calendar.MONTH, item.getMonths());
                                    }
                                    Date expireDate = calendar.getTime();
                                    deviceDao.updateExpireDate(item.getDeviceId(), expireDate);
                                } else {
                                    int amount = device.getAmount().intValue() + item.getAmount();
                                    deviceDao.updateAmount(item.getDeviceId(), amount);
                                }
                            } finally {
                                mutexLock.unlock(deviceLockKey);
                            }
                        } else {
                            throw new InternalError(String.format("未获取到设备锁: %s", notify.getOutTradeNo()));
                        }
                    }
                }
            } finally {
                mutexLock.unlock(orderLockKey);
            }
            // 云喇叭播报
            if (PaymentOrder.STATE_SUCCESS.equals(notify.getResultCode()) && ObjectUtil.isNotEmpty(notifyDeviceId)) {
                try {
                    ControlDeviceDataRequest request = new ControlDeviceDataRequest();
                    request.setProductId(notifyDeviceId.split("/")[0]);
                    request.setDeviceName(notifyDeviceId.split("/")[1]);
                    String text = String.format("微信收款%s元", StringUtils.amount(notify.getTotalFee()));
                    request.setData(JSONUtil.createObj().set("text", text).set("times", 1).toString());
                    tencentClient.ControlDeviceData(request);
                } catch (Exception e) {
                    log.error(e.getMessage());
                }
            }
        } else {
            throw new InternalError(String.format("未获取到订单锁: %s", notify.getOutTradeNo()));
        }
    }

    @Override
    public List<ChargeStandard> chargeStandard() {
        return chargeStandardDao.selectByAppId(iotAppId);
    }

    @Override
    public JSONObject transfer(HttpServletRequest request) {
        List<JSONObject> reportInfos = new ArrayList<>();
        reportInfos.add( JSONUtil.createObj()
                .set("info_type", "新店开业")
                .set("info_content", "新会员随机红包"));
        JSONObject params = JSONUtil.createObj()
                .set("appid", miniAppId)
                .set("out_bill_no", "2025042501")
                .set("transfer_scene_id", "1000")
                .set("openid", request.getHeader("x-wx-openid"))
                .set("user_name", "程道林")
                .set("transfer_amount", 1)
                .set("transfer_remark", "提现")
                .set("transfer_scene_report_infos", reportInfos);
        log.info(params.toString());
        HttpRequest httpRequest = new HttpRequest("https://api.mch.weixin.qq.com//v3/fund-app/mch-transfer/transfer-bills")
                .method(Method.POST)
                .header("Content-Type", "application/json;charset=utf8")
                .body(params.toString());
        HttpResponse response = httpRequest.execute();
        log.info("{}: {}", response.getStatus(), response.body());
        return JSONUtil.parseObj(response.body());
    }

    private long increment(String key) {
        return stringRedisTemplate.execute((RedisCallback<Long>) connection -> connection.incr(key.getBytes()));
    }

    private JSONObject sign(Config config, String appId, String prepayId) {
        String timestamp = String.valueOf(new Date().getTime() / 1000);
        String none = IdUtil.fastSimpleUUID();
        String body = String.format("%s\n%s\n%s\nprepay_id=%s\n", appId, timestamp, none, prepayId);
        log.info(body);
        String sign = config.createSigner().sign(body).getSign();
        return JSONUtil.createObj()
                .set("appId", appId)
                .set("timeStamp", timestamp)
                .set("nonceStr", none)
                .set("package", "prepay_id=" + prepayId)
                .set("signType", "RSA")
                .set("paySign", sign);
    }
}
